//--------------------------------------------------------------------------------------//
//                                                                                      //
//    __  __  __    __    __  ___   __   ___  __    ___    __  __  __  ___  ___  __     //
//   / _)/  \(  )  (  )  (  )(   \ (  ) (  ,)(  )  (  _)  (  \/  )/  \(   \(  _)(  )    //
//  ( (_( () ))(__  )(__  )(  ) ) )/__\  ) ,\ )(__  ) _)   )    (( () )) ) )) _) )(__   //
//   \__)\__/(____)(____)(__)(___/(_)(_)(___/(____)(___)  (_/\/\_)\__/(___/(___)(____)  //
//                                                                                      //
//                                                                                      //
//       Copyright by Theodor Mader, 2009                                               //
//			www.theomader.com/public/Projects.html                                      //
//--------------------------------------------------------------------------------------//

#include "threading.h"
#include "BspTree.h"

using namespace Geometry;

namespace Threading
{
	Scheduler::Scheduler( const std::vector<Vertex>& vertices, unsigned int numVerticesPerLeaf, unsigned int numThreads ) :
		m_Vertices( vertices )
	{
		m_iNumThreads = numThreads;
		m_iNumVerticesPerLeaf = numVerticesPerLeaf;
		m_iNumWaitingThreads = 0;

		InitializeCriticalSection( &m_CriticalSection );
		NewTasksAvailableEvent = CreateEvent( NULL, true, false, "" );
		AllTasksFinishedEvent = CreateEvent( NULL, true, false, "" );
	}

	Scheduler::~Scheduler() 
	{
		DeleteCriticalSection( &m_CriticalSection );
		CloseHandle( NewTasksAvailableEvent );
		CloseHandle( AllTasksFinishedEvent );
	}

	Task* Scheduler::TryGetTask()
	{
		Task* result = 0;
		if( TryEnterCriticalSection( &m_CriticalSection ) )
		{
			if( !m_TaskList.empty() )
			{
				result = m_TaskList.front();
				m_TaskList.pop_front();
			}

			if( m_TaskList.empty() )
				ResetEvent( NewTasksAvailableEvent );
			else
				SetEvent( NewTasksAvailableEvent );

			LeaveCriticalSection( &m_CriticalSection );
		}
		return result;
	}

	void Scheduler::AddTask( Task* task )
	{
		EnterCriticalSection( &m_CriticalSection );

		// add new task to task list
#ifdef DEPTH_FIRST_PROCESSING
		m_TaskList.push_front( task );
#else
		m_TaskList.push_back( task );
#endif

		SetEvent( NewTasksAvailableEvent );

		LeaveCriticalSection( &m_CriticalSection );
	}

	void Scheduler::AddTasks( const std::list<Task*>& tasks )
	{
		EnterCriticalSection( &m_CriticalSection );

#ifdef DEPTH_FIRST_PROCESSING
		m_TaskList.insert( m_TaskList.begin(), tasks.begin(), tasks.end() );
#else
		m_TaskList.insert( m_TaskList.end(), tasks.begin(), tasks.end() );
#endif
		SetEvent( NewTasksAvailableEvent );

		LeaveCriticalSection( &m_CriticalSection );
	}

	void Scheduler::IncreaseNumWaitingThreads()
	{
		EnterCriticalSection( &m_CriticalSection );
		m_iNumWaitingThreads++;

		if( m_iNumWaitingThreads == 0 && m_TaskList.empty() )
			SetEvent( AllTasksFinishedEvent );

		LeaveCriticalSection( &m_CriticalSection );
	}

	void Scheduler::DecreaseNumWaitingThreads()
	{
		InterlockedDecrement( &m_iNumWaitingThreads );
	}

	void Scheduler::StartProcessing()
	{
		std::vector<TaskData*> taskData( m_iNumThreads );
		std::vector<HANDLE> threadHandles( m_iNumThreads );

		for( unsigned int i=0; i<m_iNumThreads; i++ )
		{
			taskData[i] = new TaskData( *this );

			threadHandles[i] = CreateThread(
				NULL,
				NULL,
				ProcessTasksWorker,
				(LPVOID) &(*taskData[i]),
				NULL,
				NULL 
			);
		}

		// wait until all threads have finished
		WaitForMultipleObjects( m_iNumThreads, &threadHandles[0], true, INFINITE );

		// do some clean up
		for( unsigned int i=0; i<m_iNumThreads; i++ )
		{
			CloseHandle( threadHandles[i] );
			delete taskData[i];
		}	
	}



	DWORD __stdcall ProcessTasksWorker( LPVOID taskData )
	{
		TaskData* pData = static_cast<TaskData*>( taskData );
		Scheduler& scheduler = pData->scheduler;
		HANDLE waitHandles[] = { scheduler.NewTasksAvailableEvent, scheduler.AllTasksFinishedEvent };
		int numHandles = 2;

		while( true )
		{
			DWORD waitResult = WaitForMultipleObjects( numHandles, waitHandles, false, INFINITE );

			if( waitResult == WAIT_OBJECT_0+1 )
				return 0;
			
			Task *pTask = scheduler.TryGetTask();
			if( pTask )
			{
				scheduler.DecreaseNumWaitingThreads();

				// build node and add new tasks to task list
				std::list<Task*> newTasks = BspTree::BuildNode( *pTask, scheduler.GetVertices(), scheduler.NumVerticesPerLeaf() );
				for( std::list<Task*>::iterator it = newTasks.begin(); it != newTasks.end(); it++ )
					scheduler.AddTask( *it );

				// delete old task
				delete pTask;

				scheduler.IncreaseNumWaitingThreads();
				
			}
		}
	}
}